import math
from Compiler import LanguageCompiler
from Helpers import langRe
from Storage import LanguageStorage

__author__ = 'deadmoroz'

class Highlighter(object):
    def __init__(self):
        self.__compiler = LanguageCompiler()
        self.__storage = LanguageStorage()

    def subMode(self, lexeme, mode) :
        for childMode in mode.contains:
            if childMode.beginRe.match(lexeme):
                return childMode
        return None



    def endOfMode(self, mode_index, lexeme):
        mode = self.modes[mode_index]
        if mode.end and mode.endRe.match(lexeme):
            return 1
        if mode.endsWithParent:
            level = self.endOfMode(mode_index - 1, lexeme)
            return level + 1 if level else 0
        return 0


    def compileTerminators(self, mode, language):
        terminators = []

        for childMode in mode.contains:
            terminators.append(childMode.begin)

        for mode in reversed(self.modes):
            if mode.end:
                terminators.append(mode.end)
            if not mode.endsWithParent:
                break

        if mode.illegal:
            terminators.append(mode.illegal)

        return langRe(language, '(' + "|".join(terminators) + ')')


    def eatModeChunk(self, input, language):
        mode = self.modes[-1]
        startIndex = self.index
        if not mode.terminators:
            mode.terminators = self.compileTerminators(mode, language)
        match = mode.terminators.search(input, startIndex)
        if match:
            endIndex = max(match.start(0), startIndex)
            return (input[startIndex : endIndex], match.group(0), False)
        return (input[startIndex:], "", True)

    def keywordMatch(self, language, mode, keyword):
        if language.caseInsensitive:
            keyword = keyword.lower()
        for className in mode.keywordGroups:
            if keyword in mode.keywordGroups[className]:
                return className
        return None

    def processBuffer(self, language, buffer, mode):
        if mode.subLanguage:
            #return highlight(mode.subLanguage, buffer)
            raise Exception("Not implemented")
        return self.processKeywords(language, buffer, mode)

    def startNewMode(self, mode, lexeme):

        markup = ""
        if mode.className:
            markup = '<span class="' + mode.className + '">'

        if mode.returnBegin:
            self.result += markup
            mode.buffer = ''
        elif mode.excludeBegin:
            self.result += lexeme + markup
            mode.buffer = ''
        else:
            self.result += markup
            mode.buffer = lexeme

        self.modes.append(mode)


    def processKeywords(self, language, buffer, mode):
        if not mode.keywords:
            return buffer

        result = ''
        index = 0;

        lexeme = mode.lexemsRe.search(buffer, index) if mode.lexemsRe else None
        while lexeme:
            result += buffer[index: lexeme.start(0)]
            lexemeBody = lexeme.group(0)
            keyword = self.keywordMatch(language, mode, lexemeBody)
            if keyword:
                result += '<span class="'+ keyword +'">' + lexemeBody + '</span>'
            else:
                result += lexemeBody
            index = lexeme.end(0)
            lexeme = mode.lexemsRe.search(buffer, index)

        result += buffer[index:]
        return result

    def processModeInfo(self, language, buffer, lexeme, end):

        current_mode = self.modes[-1]
        if end:
            self.result += self.processBuffer(language, current_mode.buffer + buffer, current_mode)
            return False

        new_mode = self.subMode(lexeme, current_mode)
        if new_mode:
            self.result += self.processBuffer(language, current_mode.buffer + buffer, current_mode)
            self.startNewMode(new_mode, lexeme)
            return new_mode.returnBegin


        end_level = self.endOfMode(len(self.modes) - 1, lexeme)
        if end_level:
            markup = ''
            if current_mode.className:
                markup = '</span>'
            if current_mode.returnEnd:
                self.result += self.processBuffer(language, current_mode.buffer + buffer, current_mode) + markup
            elif current_mode.excludeEnd:
                self.result += self.processBuffer(language, current_mode.buffer + buffer, current_mode) + markup + lexeme
            else:
                self.result += self.processBuffer(language, current_mode.buffer + buffer + lexeme, current_mode) + markup
            while end_level > 1:
                markup = ""
                self.modes.pop()
                if(self.modes[-1].className):
                    markup = '</span>'
                self.result += markup
                end_level = end_level - 1

            last_ended_mode = self.modes.pop()
            self.modes[-1].buffer = ''
            if last_ended_mode.starts:
                self.startNewMode(last_ended_mode.starts, '')
            return current_mode.returnEnd

        if self.isIllegal(lexeme, current_mode):
            raise Exception('Illegal')

    def isIllegal(self, lexeme, mode) :
        return mode.illegalRe and mode.illegalRe.match(lexeme)


    def highlight(self, languageName, input):
        language = self.__storage[languageName]
        self.__compiler.compile(language)
        self.modes = [language.defaultMode]
        self.index = 0
        self.result = ""
        language.defaultMode.buffer = ""

        while True:
            mode_info = self.eatModeChunk(input, language)
            return_lexeme = self.processModeInfo(language, mode_info[0], mode_info[1], mode_info[2])
            self.index += len(mode_info[0])
            if not return_lexeme:
                self.index += len(mode_info[1])
            if mode_info[2]:
                break

        if len(self.modes) > 1:
            raise Exception('Illegal')

        return self.result
